%autosave 60
Autosaving every 60 seconds
import os
import time
import pandas as pd
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
import joblib
import os
import time
# Essential DS libraries
import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_error, roc_auc_score
from sklearn.model_selection import train_test_split
import numpy as np
import pandas as pd
from sklearn.metrics import f1_score
from sklearn.model_selection import KFold, train_test_split, StratifiedKFold
from optuna.integration import CatBoostPruningCallback
from scipy.special import logit
import seaborn as sns
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from scipy.special import logit
from scipy.stats import norm
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, r2_score, precision_recall_curve
import plotly.graph_objects as go
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
import plotly.io as pio
pio.renderers.default = "notebook"
pd.set_option("display.max_columns", None)
Итотовая работа по ML¶
- Подробности на странице на платформе
- Данные в этой работе используются такие же, как и в соревновании по ML (ДЗ 1), за исключением того, что для test датасета мы знаем метку класса "1" или "0". Обновленные данные доступны в соревновании на kaggle (файл называется test_full.csv)
path_data = "./"
# не меняйте название переменных train и test - в которых лежат датасеты train.csv и test_full.csv соответсвенно
# эти файлы будут участвовать в тестировании работы
train = pd.read_csv(path_data + "train.csv")
# test датасет запрещается видоизменять - применять к нему какую либо логику трансформации
# можно видоизменять входные данные внутри функции def get_score(dict_input) (смотри ниже)
test = pd.read_csv(path_data + "test_full.csv")
correlation_matrix = train.corr()
plt.figure(figsize=(13, 7))
sns.heatmap(
correlation_matrix,
annot=True,
cmap="coolwarm",
fmt=".2f",
xticklabels=correlation_matrix.columns,
yticklabels=correlation_matrix.columns,
cbar=False,
)
plt.xticks(rotation=45)
plt.title("Correlation Matrix")
plt.show()
C:\Users\USER\AppData\Local\Temp\ipykernel_16288\3343222316.py:1: FutureWarning: The default value of numeric_only in DataFrame.corr is deprecated. In a future version, it will default to False. Select only valid columns or specify the value of numeric_only to silence this warning. correlation_matrix = train.corr()
Место для вашего кода ↓↓↓¶
Обучение модели / написание вспомогательно кода ↓↓↓¶
filled_train = train.copy()
filled_test = test.copy()
cat_features = ["использование"]
target = ["binary_target"]
features2drop = [
"client_id",
"mrg_",
"регион",
"pack",
"зона_2",
"зона_1",
"использование",
"pack_freq",
"сегмент_arpu",
"продукт_2",
"продукт_1",
]
filtered_features = [i for i in train.columns if (i not in target and i not in features2drop)]
filled_train.drop(features2drop, axis=1, inplace=True)
filled_test.drop(features2drop, axis=1, inplace=True)
transformer = ColumnTransformer(
[
("сумма", SimpleImputer(strategy="mean"), ["сумма"]),
("частота_пополнения", SimpleImputer(strategy="mean"), ["частота_пополнения"]),
("доход", SimpleImputer(strategy="mean"), ["доход"]),
("частота", SimpleImputer(strategy="mean"), ["частота"]),
("объем_данных", SimpleImputer(strategy="mean"), ["объем_данных"]),
("on_net", SimpleImputer(strategy="mean"), ["on_net"]),
],
remainder="passthrough",
)
new_columns = [
"сумма",
"частота_пополнения",
"доход",
"частота",
"объем_данных",
"on_net",
"секретный_скор",
]
filled_train = transformer.fit_transform(train[filtered_features])
filled_train = pd.DataFrame(filled_train, columns=new_columns)
filled_train
| сумма | частота_пополнения | доход | частота | объем_данных | on_net | секретный_скор | |
|---|---|---|---|---|---|---|---|
| 0 | 14.617797 | 1.000000 | 27034.974914 | 1.000000 | 34.336000 | 26.000000 | 0.540984 |
| 1 | 32.686083 | 3.000000 | 40373.828300 | 2.000000 | 35.679126 | 45.000000 | 0.573770 |
| 2 | 25.318570 | 3.000000 | 32004.378374 | 1.414214 | 34.336000 | 277.221033 | 0.131148 |
| 3 | 70.710678 | 38.000000 | 59953.505011 | 6.403124 | 163.309522 | 203.000000 | 0.885246 |
| 4 | 9.244999 | 2.000000 | 21499.998428 | 1.414214 | 5.000000 | 277.221033 | 0.344262 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1010243 | 73.088166 | 19.000000 | 57900.865741 | 4.582576 | 34.336000 | 80.000000 | 0.934426 |
| 1010244 | 20.672445 | 2.000000 | 32166.158933 | 2.645751 | 34.336000 | 25.000000 | 0.540984 |
| 1010245 | 41.418633 | 11.500826 | 42842.276282 | 3.276499 | 34.336000 | 277.221033 | 0.049180 |
| 1010246 | 66.986417 | 10.000000 | 56223.620819 | 4.123106 | 34.336000 | 62.000000 | 1.000000 |
| 1010247 | 14.617797 | 1.000000 | 29407.442327 | 1.414214 | 34.336000 | 277.221033 | 0.098361 |
1010248 rows × 7 columns
means = dict()
for i, q in enumerate(transformer.named_transformers_.keys()):
try:
means[q] = transformer.named_transformers_[q].statistics_[0]
except:
pass
means
{'сумма': 41.418632803777605,
'частота_пополнения': 11.500825508046033,
'доход': 42842.27628197239,
'частота': 3.2764993057908094,
'объем_данных': 34.33599998932551,
'on_net': 277.2210333455478}
assert filled_train.isna().sum().sum() == 0
filled_train.loc[:, ((filled_train.corr() < -0.93) | (filled_train.corr() > 0.93)).sum() > 1] # ниче не выводит значит с корреляцией все ок
| 0 |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| ... |
| 1010243 |
| 1010244 |
| 1010245 |
| 1010246 |
| 1010247 |
1010248 rows × 0 columns
filled_train
| сумма | частота_пополнения | доход | частота | объем_данных | on_net | секретный_скор | |
|---|---|---|---|---|---|---|---|
| 0 | 14.617797 | 1.000000 | 27034.974914 | 1.000000 | 34.336000 | 26.000000 | 0.540984 |
| 1 | 32.686083 | 3.000000 | 40373.828300 | 2.000000 | 35.679126 | 45.000000 | 0.573770 |
| 2 | 25.318570 | 3.000000 | 32004.378374 | 1.414214 | 34.336000 | 277.221033 | 0.131148 |
| 3 | 70.710678 | 38.000000 | 59953.505011 | 6.403124 | 163.309522 | 203.000000 | 0.885246 |
| 4 | 9.244999 | 2.000000 | 21499.998428 | 1.414214 | 5.000000 | 277.221033 | 0.344262 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1010243 | 73.088166 | 19.000000 | 57900.865741 | 4.582576 | 34.336000 | 80.000000 | 0.934426 |
| 1010244 | 20.672445 | 2.000000 | 32166.158933 | 2.645751 | 34.336000 | 25.000000 | 0.540984 |
| 1010245 | 41.418633 | 11.500826 | 42842.276282 | 3.276499 | 34.336000 | 277.221033 | 0.049180 |
| 1010246 | 66.986417 | 10.000000 | 56223.620819 | 4.123106 | 34.336000 | 62.000000 | 1.000000 |
| 1010247 | 14.617797 | 1.000000 | 29407.442327 | 1.414214 | 34.336000 | 277.221033 | 0.098361 |
1010248 rows × 7 columns
def woe_linearization(df):
df = df.copy()
df["доход"] = np.log1p(df["доход"].values)
df["сумма"] = np.clip(df["сумма"].values, 10, 50)
df["секретный_скор"] = np.sqrt(df["секретный_скор"].values)
return df
model = LogisticRegression()
scaler = StandardScaler()
transformed_train = woe_linearization(filled_train)
model.fit(scaler.fit_transform(transformed_train.values), train[target].values[:, 0])
LogisticRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
# ваш код
row_input = {
'client_id': 1010348,
'регион': 'Нептун',
'использование': '>24LY',
'сумма': 31.69021931132696,
'частота_пополнения': 16.0,
'доход': 42653.164535362455,
'сегмент_arpu': 441.45,
'частота': 4.242640687119285,
'объем_данных': 32.01562118716424,
'on_net': np.nan,
'продукт_1': 12.0,
'продукт_2': np.nan,
'зона_1': np.nan,
'зона_2': np.nan,
'mrg_': False,
'секретный_скор': 0.6065573770491803,
'pack': 'трафик: 100 (условие) 40mb,_сутки',
'pack_freq': 3.0,
'binary_target': 0
}
row_input['продукт_2'] is np.nan
True
код логики production функции ↓↓↓¶
Ограничение на число CPU в модели внутри функции def get_score - 1 ядро, это означает что нужно ставить следующие параметры (Модель должна на одном CPU) Если вы обучались на нескольких ядрах (или GPU) - поменяйте параметры в уже обученной модели, чтобы в production она использовала только одно ядро
- это нужно явно показать в коде
- для уже обученных моделей:
- для catboost - model.predict(thread_count=1)
- для lightgbm - model.set_params(n_jobs=1) и затем model.get_params().get('n_jobs')
- для xgboost - model.set_params(n_jobs=1) и затем model.get_params()['n_jobs']
- для других классификаторов смотрите документацию
Логика работы¶
Будем использовать logreg как наиболее быструю модель. Т.к нас интересуют только предсказание класса, можем отказаться от сигмоиды и в качестве трешхолда брать logit(p). Оптимальная p подбиралось на трейновой выборке
Также для ускорения откажемся от неинформативных признаков, а оставшиеся признаки линеаризуем по WoE (не все тк время).
Неинформативные признаки выбирались исходя из размера коэфициентов, и признаки с наименьшимим коэфициентами убирались до тех пор пока побивался скор
coeff = model.coef_[0]
bias = model.intercept_[0]
scaler_mean = list(scaler.mean_)
scaler_scale = list(scaler.scale_)
selected_features = ['сумма', 'частота_пополнения', 'доход', 'частота',
'объем_данных', 'on_net', 'секретный_скор']
TRESHOLD = logit(0.31679194286337614)
filled_train
| сумма | частота_пополнения | доход | частота | объем_данных | on_net | секретный_скор | |
|---|---|---|---|---|---|---|---|
| 0 | 14.617797 | 1.000000 | 27034.974914 | 1.000000 | 34.336000 | 26.000000 | 0.540984 |
| 1 | 32.686083 | 3.000000 | 40373.828300 | 2.000000 | 35.679126 | 45.000000 | 0.573770 |
| 2 | 25.318570 | 3.000000 | 32004.378374 | 1.414214 | 34.336000 | 277.221033 | 0.131148 |
| 3 | 70.710678 | 38.000000 | 59953.505011 | 6.403124 | 163.309522 | 203.000000 | 0.885246 |
| 4 | 9.244999 | 2.000000 | 21499.998428 | 1.414214 | 5.000000 | 277.221033 | 0.344262 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1010243 | 73.088166 | 19.000000 | 57900.865741 | 4.582576 | 34.336000 | 80.000000 | 0.934426 |
| 1010244 | 20.672445 | 2.000000 | 32166.158933 | 2.645751 | 34.336000 | 25.000000 | 0.540984 |
| 1010245 | 41.418633 | 11.500826 | 42842.276282 | 3.276499 | 34.336000 | 277.221033 | 0.049180 |
| 1010246 | 66.986417 | 10.000000 | 56223.620819 | 4.123106 | 34.336000 | 62.000000 | 1.000000 |
| 1010247 | 14.617797 | 1.000000 | 29407.442327 | 1.414214 | 34.336000 | 277.221033 | 0.098361 |
1010248 rows × 7 columns
# Это шаблон функции, которая будет тестироваться далее
# не меняйте формат input и output, а так же название функции
# в этой функции нужно сделать так, чтобы ваша модель использовала только 1 CPU при скоринге
from line_profiler import profile
from math import log1p, exp
row_input = {
'client_id': 1010348,
'регион': 'Нептун',
'использование': '>24LY',
'сумма': 31.69021931132696,
'частота_пополнения': 16.0,
'доход': 42653.164535362455,
'сегмент_arpu': 441.45,
'частота': 4.242640687119285,
'объем_данных': 32.01562118716424,
'on_net': np.nan,
'продукт_1': 12.0,
'продукт_2': np.nan,
'зона_1': np.nan,
'зона_2': np.nan,
'mrg_': False,
'секретный_скор': 0.6065573770491803,
'pack': 'трафик: 100 (условие) 40mb,_сутки',
'pack_freq': 3.0,
'binary_target': 0
}
def get_score(dict_input):
# # место для вашего кода
for col in filtered_features:
if dict_input[col] != dict_input[col]:
dict_input[col] = means[col]
dict_input["доход"] = log1p(max(0, dict_input["доход"]))
dict_input["сумма"] = min(max(dict_input["сумма"], 10), 50)
dict_input["секретный_скор"] **= 0.5
data = np.array([dict_input[i] for i in selected_features])
data-=scaler_mean
data/=scaler_scale
binary_prediction = int(sum(coeff*data)+bias > TRESHOLD)
# вот это нельзя менять, иначе сломаются проверки
return {
"client_id": dict_input['client_id'],
"prediction": binary_prediction,
}
get_score(row_input)
%load_ext line_profiler
%lprun -f get_score get_score(row_input)
Timer unit: 1e-07 s
Total time: 7.29e-05 s
File: C:\Users\USER\AppData\Local\Temp\ipykernel_16288\4124684817.py
Function: get_score at line 31
Line # Hits Time Per Hit % Time Line Contents
==============================================================
31 def get_score(dict_input):
32 # # место для вашего кода
33 8 48.0 6.0 6.6 for col in filtered_features:
34 7 67.0 9.6 9.2 if dict_input[col] != dict_input[col]:
35 1 14.0 14.0 1.9 dict_input[col] = means[col]
36
37 1 33.0 33.0 4.5 dict_input["доход"] = log1p(max(0, dict_input["доход"]))
38 1 18.0 18.0 2.5 dict_input["сумма"] = min(max(dict_input["сумма"], 10), 50)
39 1 24.0 24.0 3.3 dict_input["секретный_скор"] **= 0.5
40
41
42 1 180.0 180.0 24.7 data = np.array([dict_input[i] for i in selected_features])
43
44 1 129.0 129.0 17.7 data-=scaler_mean
45 1 57.0 57.0 7.8 data/=scaler_scale
46 1 134.0 134.0 18.4 binary_prediction = int(sum(coeff*data)+bias > TRESHOLD)
47
48 # вот это нельзя менять, иначе сломаются проверки
49 1 13.0 13.0 1.8 return {
50 1 7.0 7.0 1.0 "client_id": dict_input['client_id'],
51 1 5.0 5.0 0.7 "prediction": binary_prediction,
52 }
# подсказка
# следите за своей предобработкой
# основное время работы функции должна занимать модель
# можно использовать специальное расширение для понимания - сколько выполняется по времени каждая часть кода
# row_input = ...
# pip install line_profiler
# from line_profiler import profile
# %load_ext line_profiler
# %lprun -f get_score get_score(row_input)
Тут нужно написать код для пятого пункта дз ↓↓↓¶
проверка качества предсказания модели (без знания о целевой переменной, только на основе входных данных и выходного предсказания)
Для проверки стабильности модели будем использовать psi¶
from scipy.stats import entropy
def get_empiric_pdf(df, column, ranges, bins=20):
pdf, ranges, _ = plt.hist(
df.loc[~train.использование.isna(), column],
range=ranges,
bins=bins,
density=True,
)
plt.close()
return (pdf + 1e-7) * ranges[1]
def calc_psi(history_dist, new_dist):
return entropy(history_dist, new_dist) + entropy(new_dist, history_dist)
history_dist, new_dist = get_empiric_pdf(
train, "сумма", (train["сумма"].min(), train["сумма"].max())
), get_empiric_pdf(test, "сумма", (train["сумма"].min(), train["сумма"].max()))
calc_psi(history_dist, new_dist)
7.284736746771299e-05
Пример графиков стабильности на всем датасете. В случаи если признаки стабильны в статусе признака выводится OK
import math
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def plot_psi(train_df, test_df, features, step, bins=20):
num_features = len(features)
num_cols = 4
num_rows = math.ceil(num_features / num_cols)
fig = make_subplots(rows=num_rows, cols=num_cols, subplot_titles=features, vertical_spacing=0.15)
for i, feature in enumerate(features):
row = i // num_cols + 1
col = i % num_cols + 1
psi = []
history_dist = get_empiric_pdf(train_df, feature, (train_df[feature].min(), train_df[feature].max()), bins)
for i in range(step, len(test_df), step):
new_dist = get_empiric_pdf(test_df[i: i + step], feature, (train_df[feature].min(), train_df[feature].max()), bins)
psi.append(calc_psi(history_dist, new_dist))
color = 'green' if max(psi) < 0.1 else 'red'
title = f'{feature} status: <span style="color:{color}">{"OK" if color == "green" else "ALERT"}</span>'
fig.add_trace(go.Scatter(x=list(range(step, len(test_df), step)), y=psi, mode='lines', name=feature), row=row, col=col)
fig.update_annotations({'text': title}, selector=dict(text=feature))
print(feature)
fig.update_layout(height=500*num_rows, width=1600, title_text=f'PSI plot', showlegend=True, legend=dict(itemsizing='constant'), template='ggplot2')
fig.show()
plot_psi(train, test, features=['доход', 'сумма', 'частота_пополнения', 'секретный_скор', 'объем_данных', 'сумма'], step=10_000)
доход сумма частота_пополнения секретный_скор объем_данных сумма
Здесь предполагается, что мы проводим мониторинг стабильности каждые 50_000 наблюдений
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tqdm import tqdm
import plotly.io as pio
os.makedirs("plots", exist_ok=True)
# Parameters
step = 50_000
bins = 20
features = [
"доход",
"сумма",
"частота_пополнения",
"секретный_скор",
"объем_данных",
"сумма",
]
columns = list(set(train.columns) - {"binary_target"})
new_data = []
psi = {feature: [] for feature in features}
preds = []
num_features = len(features)
num_cols = 3
num_rows = math.ceil(num_features / num_cols)
fig = make_subplots(
rows=num_rows, cols=num_cols, subplot_titles=features, vertical_spacing=0.15
)
# Precompute the history distribution for all features
history_dists = {}
for feature in features:
history_dists[feature] = get_empiric_pdf(
train, feature, (train[feature].min(), train[feature].max()), bins
)
for i, row in enumerate(tqdm(test.itertuples(index=False), total=len(test))):
dict_input_ = {
"client_id": row.client_id,
"регион": row.регион,
"использование": row.использование,
"сумма": row.сумма,
"частота_пополнения": row.частота_пополнения,
"доход": row.доход,
"сегмент_arpu": row.сегмент_arpu,
"частота": row.частота,
"объем_данных": row.объем_данных,
"on_net": row.on_net,
"продукт_1": row.продукт_1,
"продукт_2": row.продукт_2,
"зона_1": row.зона_1,
"зона_2": row.зона_2,
"mrg_": row.mrg_,
"секретный_скор": row.секретный_скор,
"pack": row.pack,
"pack_freq": row.pack_freq,
}
new_data.append([dict_input_[i] for i in columns if i != "binary_target"])
score_result = get_score(dict_input_)
preds.append(score_result["prediction"])
if (i + 1) % step == 0:
for j, feature in enumerate(features):
row_num = j // num_cols + 1
col_num = j % num_cols + 1
new_data_df = pd.DataFrame(new_data, columns=columns)
new_dist = get_empiric_pdf(
new_data_df.iloc[-step:],
feature,
(train[feature].min(), train[feature].max()),
bins,
)
new_psi = calc_psi(history_dists[feature], new_dist)
psi[feature].append(new_psi)
if new_psi > 0.1:
print(f"Alert feature {feature}: psi>0.1 model can be unstable")
color = "green" if max(psi[feature]) < 0.1 else "red"
title = f'{feature} status: <span style="color:{color}">{"OK" if color == "green" else "ALERT"}</span>'
fig.add_trace(
go.Scatter(
x=list(range(step, (i + 1) + step, step)),
y=psi[feature],
mode="lines",
name=feature,
),
row=row_num,
col=col_num,
)
fig.update_annotations({"text": title}, selector=dict(text=feature))
if i < step:
continue
fig.update_layout(
height=500 * num_rows,
width=1600,
title_text="PSI Plot",
showlegend=False,
template="ggplot2",
)
fig.show()
19%|█▉ | 95019/497586 [00:02<00:07, 51311.99it/s]
30%|██▉ | 147818/497586 [00:04<00:07, 47062.54it/s]
39%|███▉ | 193565/497586 [00:07<00:08, 37538.89it/s]
49%|████▉ | 245779/497586 [00:11<00:07, 34786.43it/s]
60%|█████▉ | 298291/497586 [00:16<00:05, 35877.41it/s]
70%|██████▉ | 346434/497586 [00:20<00:04, 30524.05it/s]
80%|████████ | 398485/497586 [00:26<00:03, 28986.49it/s]
89%|████████▉ | 444879/497586 [00:32<00:02, 22919.83it/s]
100%|██████████| 497586/497586 [00:38<00:00, 12888.78it/s]
Место для вашего кода ↑↑↑¶
тест 1: проверка input модели (1)¶
row_input = {
'client_id': 1010348,
'регион': 'Нептун',
'использование': '>24LY',
'сумма': 31.69021931132696,
'частота_пополнения': 16.0,
'доход': 42653.164535362455,
'сегмент_arpu': 441.45,
'частота': 4.242640687119285,
'объем_данных': 32.01562118716424,
'on_net': np.nan,
'продукт_1': 12.0,
'продукт_2': np.nan,
'зона_1': np.nan,
'зона_2': np.nan,
'mrg_': False,
'секретный_скор': 0.6065573770491803,
'pack': 'трафик: 100 (условие) 40mb,_сутки',
'pack_freq': 3.0,
'binary_target': 0
}
# если завершилось без ошибок - тест пройден
get_score(row_input)
{'client_id': 1010348, 'prediction': 0}
тест 2: проверка input модели (2)¶
row_input = {
'client_id': -999,
'регион': 'Калифорния',
'использование': '<2года',
'сумма': -999,
'частота_пополнения': -16.0,
'доход': -42653.164535362455,
'сегмент_arpu': -441.45,
'частота': 0.1,
'объем_данных': -1,
'on_net': np.nan,
'продукт_1': -12.0,
'продукт_2': np.nan,
'зона_1': np.nan,
'зона_2': np.nan,
'mrg_': True,
'секретный_скор': -0.6065573770491803,
'pack': 'трафик Тройное условие 123',
'pack_freq': -3.0,
'binary_target': -0.1
}
# если завершилось без ошибок - тест пройден
get_score(row_input)
{'client_id': -999, 'prediction': 1}
тест 3: проверка output модели¶
get_score_result = get_score(row_input)
# если завершилось без ошибок - тест пройден
assert get_score_result['prediction'] in [0, 1]
# если завершилось без ошибок - тест пройден
assert not bool(set(get_score_result.keys()) - set(['client_id', 'prediction']))
тест 4: мониторинг времени (1)¶
- зеленый цвет названия графика означает - что тест пройден
- красный цвет названия графика означает - что тест не пройден
%%time
# тут ничего менять не нужно
times = []
preds = []
for row in tqdm(test.itertuples(index=False), total=len(test)):
dict_input_ = {
"client_id": row.client_id,
"регион": row.регион,
"использование": row.использование,
"сумма": row.сумма,
"частота_пополнения": row.частота_пополнения,
"доход": row.доход,
"сегмент_arpu": row.сегмент_arpu,
"частота": row.частота,
"объем_данных": row.объем_данных,
"on_net": row.on_net,
"продукт_1": row.продукт_1,
"продукт_2": row.продукт_2,
"зона_1": row.зона_1,
"зона_2": row.зона_2,
"mrg_": row.mrg_,
"секретный_скор": row.секретный_скор,
"pack": row.pack,
"pack_freq": row.pack_freq,
}
# оценка времени работы функции скоринга
stime = time.time()
score_result = get_score(dict_input_)
times.append((time.time() - stime))
# сохранение предиктов для будущей оценки
preds.append(score_result['prediction'])
times = np.array(times) * 1000
100%|██████████| 497586/497586 [00:06<00:00, 74522.17it/s]
CPU times: total: 6.7 s Wall time: 6.71 s
%%timeit
get_score(dict_input_)
8.89 µs ± 410 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
print(np.quantile(times, 0.98995))
0.0
model.coef_
array([[ 0.40504178, -0.25728984, -0.0508848 , 0.19696517, 0.1421343 ,
0.10493687, -1.98622733]])
Отрисовка оценки времени работы¶
plt_vals = plt.hist(times, bins=np.linspace(np.quantile(times, 0.001), np.quantile(times, 0.999)))
plt.vlines(0.10, 0, plt_vals[0].max() * 0.9, color='green', linestyle=':')
plt.text(0.10, plt_vals[0].max() * 0.9, s='baseline = 0.10 ms', color='black', rotation=0)
plt.vlines(np.quantile(times, 0.95), 0, plt_vals[0].max() * 0.6, color='red', linestyle=':')
plt.vlines(np.quantile(times, 0.99), 0, plt_vals[0].max() * 0.5, color='red', linestyle=':')
plt.text(
np.quantile(times, 0.95), plt_vals[0].max() * 0.6,
s=f'моя 0.95% = {np.quantile(times, 0.95):0.3f} ms', color='black', rotation=0
)
plt.text(
np.quantile(times, 0.99), plt_vals[0].max() * 0.5,
s=f'моя 0.99% = {np.quantile(times, 0.99):0.3f} ms', color='black', rotation=0
)
plt.title(
f'ваше время ответа в ms\n95% = {np.quantile(times, 0.95):0.3f} ms\n99% = {np.quantile(times, 0.99):0.3f} ms',
y=1.05,
color=(
'green'
if (round(np.quantile(times, 0.95), 3) <= 0.10) & (round(np.quantile(times, 0.99), 3) <= 0.10)
else 'red'
)
)
plt.xlabel('время работы в ms')
plt.tight_layout()
plt.show()
# Тут мы должны увидеть, что модель выдает предсказания быстрее ожидаемого
# Наше предсказание должно быть быстрее baseline в каждом случае
тест 5: мониторинг времени (2)¶
row_input = {
'client_id': -100,
'регион': 'Plan_1',
'использование': '<000',
'сумма': 0,
'частота_пополнения': 0.0,
'доход': np.nan,
'сегмент_arpu': np.nan,
'частота': np.nan,
'объем_данных': np.nan,
'on_net': np.nan,
'продукт_1': np.nan,
'продукт_2': np.nan,
'зона_1': np.nan,
'зона_2': np.nan,
'mrg_': True,
'секретный_скор': 0.0,
'pack': np.nan,
'pack_freq': np.nan,
}
%%timeit
_ = get_score(row_input)
# Ожидаем скорость быстрее 110 µs (<110 µs)
8.14 µs ± 77.6 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
тест 6: мониторинг f1 score¶
- зеленый цвет названия графика означает, что тест пройден
- красный цвет названия графика означает, что тест не пройден
# Ожидаемая метрика >= 0.6450
plot_vals = plt.hist(preds)
plt.title(
f"метрика f1_score: {f1_score(test['binary_target'], preds):0.5f}",
color=('red' if f1_score(test['binary_target'], preds) < 0.6450 else 'green')
)
plt.text(0, plot_vals[0][0], s=f"{plot_vals[0][0]:0.0f}")
plt.text(1, plot_vals[0][-1], s=f"{plot_vals[0][-1]:0.0f}")
plt.show()
тест 7: Code Style¶
- воспользуемся библиотекой для оценки качества кода https://github.com/nbQA-dev/nbQA?tab=readme-ov-file#-examples
- нужно установить актуальную версию pip install flake8
- нужно установить актуальную версию pip install -U nbqa
- в отдельной ячейке нужно выполнить команду: !nbqa flake8 ML_итоговая_шаблон.ipynb (см. эту команду ниже)
- поставьте вместо ML_итоговая_шаблон.ipynb ваше название ноутбука
- запрещается менять эту команду, за исключением названия ноутбука
os.system("nbqa flake8 --extend-ignore=W291,E501 teta_ML_Долгушев.ipynb & echo 'everything done'")
0
# ячейка выше должна отпринтовать следующее
# everything done
# 0
# если помимо этого есть и другие строчки - это ошибки по pep8, их нужно исправлять
# например строчка teta_ML_итоговая_шаблон.ipynb:cell_27:1:1: F401 'scipy' imported but unused
# означает, что был бесполезный import scipy, который нигде не использовался
Если все тесты пройдены успешно, вы можете претендовать на наивысшый балл (⌐■_■)¶
print('Ура!')
Ура!